#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File    :   application.py
@Time    :   2020/04/02 10:19:11
@Author  :   Xiong qiufeng
@Version :   1.0
@Contact :   1528399071@qq.com
@License :   (C)Copyright 2017-2018, Liugroup-NLPR-CASIA
@Desc    :   None
'''

import json
from collections import OrderedDict
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *

import os
from threading import Thread
import serial
import serial.tools.list_ports
from common.libs.Log import logging
import time

# from common.libs.ComDeal import WATER_CMD,ComDeal
from common.libs.MathFunc import MathFunc
from common.libs.Helper import Helper

import configparser
import threading
import binascii
import re
import random


import PyQt5_stylesheets
# 图标显示
from six import unichr
import qtawesome as qta
from ui.application import Ui_MainWindow
# 图片资源文件导入
from ui import images

from dlg.axisSetDialog import axisSetDialog
from dlg.tipDialog import tipDialog
from dlg.logDialog import logDialog
from dlg.writeConfDialog import writeConfDialog
from dlg.moveDialog import MoveWidget

# 皮肤
import PyQt5_stylesheets

# import pysnooper
filename = '%s/log/%s_debug.log' % (os.getcwd(),
                                    time.strftime("%Y%m%d", time.localtime()))
version = 'v1.0.1'

# 配置文件
# CONFIG_PATH = os.getcwd()+'/conf/axis.json'
from conf import conf

CONFIG_PATH = conf.CONFIG_PATH
with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
    CONFIG = json.loads(f.read(), object_pairs_hook=OrderedDict)


class main(QMainWindow, Ui_MainWindow):

    # 串口接收定时器开启信号
    signal_serial_timer = pyqtSignal()

    def __init__(self, parent=None):
        super(main, self).__init__()
        self.setupUi(self)

        # 设置字体
        self.font = QFont()
        self.font.setFamily("Agency FB")
        self.font.setPointSize(CONFIG['font'])

        # 尺寸
        self.size = QSize(300, 200)

        # 界面尺寸
        # w = GetSystemMetrics(0)
        # h = GetSystemMetrics(1)
        # self.setWindowState(Qt.WindowMaximized)
        # self.setMinimumSize(768, 480)
        # self.setMinimumSize(768, 480)
        # 最大化
        # if conf.SYSTEM == "windows":
        #     self.setWindowState(Qt.WindowMaximized)

        self.setWindowTitle('控制器')
        self.setWindowTitle(CONFIG['appName'])
        self.setWindowIcon(QIcon(':icons/img/app.ico'))

        # 移动界面
        self.moveDialog = MoveWidget()
        self.moveDialog.signal_serial_send.connect(self.sendCmd)
        self.pushButton_move.clicked.connect(self.moveShow)

        # 自定义界面
        self.axisSetDialog = axisSetDialog()
        self.axisSetDialog.signal_name_changed.connect(self.moveDialog.axisTextChangedName)
        self.axisSetDialog.signal_pos_changed.connect(self.moveDialog.axisTextChangedPos)
        self.axisSetDialog.signal_serial_changed.connect(self.serialChanged)
        self.pushButton_axis_set.clicked.connect(self.axisSet)


        # 日志窗口
        self.logDlg = logDialog()
        self.pushButton_log.clicked.connect(self.logDlg.show)

        # 写入配置窗口
        self.writeConfDlg = writeConfDialog()
        self.writeConfDlg.signal_send.connect(self.sendCmd)
        self.pushButton_write_conf.clicked.connect(self.writeConfDlg.show)

        # 串口
        self.signal_serial_timer.connect(self.serialRecvTimer)
        self.serial = serial.Serial()
        self.serialInit()

        self.pushButton_move.setFont(self.font)
        self.pushButton_axis_set.setFont(self.font)
        self.pushButton_log.setFont(self.font)
        self.pushButton_write_conf.setFont(self.font)

        self.pushButton_move.setMinimumSize(self.size)
        self.pushButton_axis_set.setMinimumSize(self.size)
        self.pushButton_log.setMinimumSize(self.size)
        self.pushButton_write_conf.setMinimumSize(self.size)

        self.skinLoad()
        self.center()

    def moveShow(self):
        self.moveDialog.show()

    def skinLoad(self):
        self.setStyleSheet(
            PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_Dark"))
        self.axisSetDialog.setStyleSheet(
            PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_Dark"))
        self.writeConfDlg.setStyleSheet(
            PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_Dark"))
        self.moveDialog.setStyleSheet(
            PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_Dark"))

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def serialInit(self):
        '''初始化串口'''
        self.serial.timeout = 0.5  # make sure that the alive event can be checked from time to time
        self.thread = None
        self.serial_alive = False
        self.serial.port = None
        self.serial.baudrate = '4800'
        self.serial.bytesize = 8
        self.serial.parity = 'N'
        self.serial.stopbits = 1
        self.serial.dtr = True
        self.serial.rts = True
        self.recv = ''

        self.label_com.setStyleSheet('image: url(:icons/img/com-offline.png)')
        self.label_com.setFont(self.font)
        self.label_com.setMinimumSize(self.size)
        self.thread_find_open = threading.Thread(target=self.serialOpen)
        self.thread_find_open.setDaemon(True)
        self.thread_find_open.start()

    def serialChanged(self, port, baud):
        CONFIG['serial']['port'] = port
        CONFIG['serial']['baud'] = baud

    def serialUpdate(self):
        com_list = []
        port_list = serial.tools.list_ports.comports()
        for p in port_list:
            port = p.device
            com_list.append(port)
        self.comboBox_com_port.addItems(com_list)

    def serialOpen(self):
        '''搜索串口并打开串口'''
        while not self.serial_alive:
            port = CONFIG['serial']['port']
            baud = CONFIG['serial']['baud']
            if port and baud:
                self.serial.port = port
                self.serial.baudrate = baud
                try:
                    self.serial.open()
                    self.label_com.setStyleSheet(
                        'image: url(:icons/img/com.png)')
                    logging.info('打开串口: %s-%s' %
                                 (self.serial.port, self.serial.baudrate))
                except serial.SerialException as e:
                    pass
                    # logging.error(e)
                else:
                    self.serial_alive = True
                    self.signal_serial_timer.emit()

    def serialClose(self):
        if self.serial.isOpen():
            self.serial_alive = False
            self.serial.close()
            self.label_com.setStyleSheet(
                'image: url(:icons/img/com-offline.png)')

            # 开串口查询线程
            self.thread_find_open = threading.Thread(target=self.serialOpen)
            self.thread_find_open.setDaemon(True)
            self.thread_find_open.start()

    def sendCmd(self, axis, _type, data):
        """ 命令格式  轴+数据长度+命令类型+数据+校验+结束
        """
        length = len(data) + 3
        cmd = axis+chr(length)+_type+data
        cmd_bytes = bytes(cmd, encoding='utf-8')
        cmd_hex = [x for x in cmd_bytes]
        crc = sum(cmd_hex) & 0x00000f
        cmd_hex.append(crc)
        cmd_hex.append(0x23)
        self.SerialWrite(
            ''.join([str(hex(x)).replace('0x', '').zfill(2) for x in cmd_hex]))

    def SerialWrite(self, cmd):
        '''串口写入,十六进制格式字符串'''
        if self.serial_alive:
            if self.serial.isOpen():
                data = binascii.unhexlify(cmd)
                self.serial.write(data)
                logging.info(
                    'tx ----> '+' '.join([cmd[i * 2:i * 2 + 2] for i in range(0, int(len(cmd) / 2))]))
                logging.info(data)
        else:
            if self.moveDialog.serial_flag:
                self.moveDialog.readInfo_flag = False
                self.moveDialog.timer_readinfo.stop()
                self.moveDialog.readInfoMessageBox.accept()

                self.moveDialog.move_one_flag = False
                self.moveDialog.timer_move.stop()
            QMessageBox.information(
                self, '通信提示', '通信异常,请检查通信接口', QMessageBox.Yes)

    def serialRecvTimer(self):
        """串口接收定时器"""
        self.timer_recv = QTimer()
        self.timer_recv.timeout.connect(self.serialRecv)
        self.timer_recv.start(CONFIG["serialRecvTimer"])
        self.label_com.setStyleSheet('image: url(:icons/img/com.png)')

        # 串口开启时自动读取信息
        self.moveDialog.readInfo()

    def serialRecv(self):
        """串口接收"""
        if self.serial_alive:
            try:
                n = self.serial.inWaiting()
                if n:
                    b = self.serial.read(n)
                    # 将byte转换为十六进制字符串
                    data = str(binascii.b2a_hex(b).decode('utf-8'))
                    self.recv += data.upper()
                    # try:
                    self.serialCmdDeal()
                    # except Exception as e:
                        # logging.info(e)
            except Exception as e:
                logging.warn("[串口异常]")
                logging.warn(e)
                # self.label_comStatus.setStyleSheet('image: url(:icons/img/com-offline.png)')
                # 串口异常, 关闭串口
                self.serialClose()

    def correctCmdDeal(self, recv):
        if self.writeConfDlg.write_flag:
            self.writeConfDlg.dealRecv(recv)
            return
        if self.moveDialog.serial_flag:
            self.moveDialog.serial_deal(recv)
            return
        cmd_bytes = binascii.unhexlify(recv)
        logging.info(cmd_bytes)
        cmd_start = recv[0:2]
        # 获取当前是哪个轴
        axis = chr(int(cmd_start, 16))
        # 清除超时标志
        self.readInfo_timeout_cnt = 0
        self.move_one_timeout_cnt = 0
        cmd_len = recv[2:4]
        logging.info("[cmd_len]:%s" % cmd_len)
        if cmd_len == 4:
            if self.move_one_flag:
                self.move_one_step = "GET_STATUS"
                logging.info("[开始移动]")
        if cmd_len == 6:  # 走步状态返回
            logging.info("[走步状态]")
            state = recv[4:6]
            if self.move_one_flag:
                if state == "63":  # c : 走步完成
                    self.move_one_step = "DONE"
                    logging.info("[%s轴]: 走步完成" % axis)
                if state == "6E":  # n : 走步中
                    self.move_one_step = "GET_POSITION"  # 处于移动中, 进入获取位置步骤
                    logging.info("[%s轴]: 走步中..." % axis)
        if cmd_len == 9:  # 向上位机报告此轴当前位置
            logging.info("[位置信息]")
            pos = recv[4:12]
            pos = MathFunc.hexStrToNeg(pos) / 1000
            self.doubleBox_current_dict[axis].setValue(pos)
            logging.info("[%s轴]:%s" % (axis, pos))
            if self.move_one_flag:  # 单轴移动
                if self.move_one_step == "GET_POSITION":
                    self.move_one_step = "GET_STATUS"
                if self.move_one_step == "DONE":
                    self.move_one_flag = False
                    logging.info("移动完成!!！")
                    QMessageBox.information(
                        self, '提示', '%s \r\n移动完成' % self.axis_name_dict[self.move_one_axis], QMessageBox.Yes)
        # if self.readInfo_flag:
        #     self.readInfo_step += 1
        logging.info("*"*20 + "Deal Done" + "*"*20)

    # @pysnooper.snoop(filename)
    def serialCmdDeal(self):
        '''串口处理命令，监测校验和是否正确'''
        logging.info(
            'rx <---- '+' '.join([self.recv[i * 2:i * 2 + 2] for i in range(0, int(len(self.recv) / 2))]))
        # 命令超出最大范围, 清空
        if len(self.recv)>100:
            logging.info("[命令超出最大范围, 清空!!!]")
            self.recv=""
            return

        recv = self.recv
        start_re = re.compile('.*?(61|62|63|64|65|66|67|68|69|6A).*?')
        end_re = re.compile('.*?(23).*?')
        for i in range(len(start_re.findall(recv))):
            # 匹配开始和结尾
            start_res = start_re.match(recv)
            end_res = end_re.match(recv)
            # 获取开始和结尾的索引值
            if start_res:
                start_idx = start_res.start(1)
            else:
                start_idx = -1
            if end_res:
                end_idx = end_res.start(1)
            else:
                end_idx = -1
            if start_idx == 0 and end_idx > 4:
                # 判断实际长度和理论长度
                real_len = int(len(recv) / 2) - 2
                theory_len = int(recv[start_idx+2:start_idx+4])
                # logging.info("[real_len]:%s" % real_len)
                # logging.info("[theory_len]:%s" % theory_len)
                # 实际长度>=理论长度
                if real_len >= theory_len :
                    cmd = recv[start_idx:theory_len*2+4]
                    # logging.info("[start_idx]:%s" % start_idx)
                    # logging.info("[end_idx]:%s" % end_idx)
                    # logging.info("[cmd]:%s" % cmd)
                    self.recv = recv[theory_len*2+4:]
                    real_len = int(len(cmd) / 2) - 2
                    theory_len = int(cmd[2:4])
                    if theory_len == real_len:
                        logging.info("[长度正确]")
                        # 处理正常命令
                        self.correctCmdDeal(cmd)
                    else:
                        logging.info("[多条命令重合]")
            else:
                self.recv = recv[start_idx:]

    def axisSet(self):
        res = self.axisSetDialog.exec()

    def axisTextChangedName(self, tp, txt):
        self.tabWidget.setTabText(self.tab_index_dict[tp],  txt)

    def axisTextChangedPos(self, tp, txt):
        self.combox_target_dict[tp].clear()
        self.combox_target_dict[tp].addItems(txt.split(','))

    def comboxTargetChanged(self):
        obj = self.sender()  # 获取发射信号的对象
        for k in self.combox_target_dict:
            if self.combox_target_dict[k] == obj:
                try:
                    self.doubleBox_target_dict[k].setValue(
                        float(self.combox_target_dict[k].currentText()))
                except:
                    pass
                break

    def axisChangedEnable(self, tp, enable):
        if enable == True:
            self.combox_target_dict[tp].setEnabled(True)
            self.doubleBox_current_dict[tp].setEnabled(True)
            self.doubleBox_target_dict[tp].setEnabled(True)
            self.btn_move_dict[tp].setEnabled(True)
        if enable == False:
            self.combox_target_dict[tp].setEnabled(False)
            self.doubleBox_current_dict[tp].setEnabled(False)
            self.doubleBox_target_dict[tp].setEnabled(False)
            self.btn_move_dict[tp].setEnabled(False)


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    form = main()
    form.show()
    app.exec_()
